//
//  OAuth.m
//
//  Created by Jaanus Kase on 12.01.10.
//  Copyright 2010. All rights reserved.
//

#import "OAuth.h"
#include <CommonCrypto/CommonDigest.h>
#import "OAHMAC_SHA1SignatureProvider.h"
#import "NSString+URLEncoding.h"
#import "ASIFormDataRequest.h"
#import "StringUtil.h"

@interface OAuth()
-(void)prepareBasicOAuthDic;
@end


@implementation OAuth

@synthesize consumer=_consumer;
@synthesize basicOAuthParam=_basicOAuthDic;

#pragma mark -
#pragma mark Init and dealloc

/**
 * Initialize an OAuth context object with a given consumer key and secret. These are immutable as you
 * always work in the context of one app.
 */
- (id) init {
    self = [super init];
	if (self) {
		srandom(time(NULL)); // seed the random number generator, used for generating nonces
	}	
	return self;
}

- (void) dealloc {
    [_consumer release];
	[super dealloc];
}


#pragma mark -
#pragma mark Public methods

/**
 * An extra method that lets the caller override the token secret used to sign the header. This is determined automatically
 * most of the time based on if our token has been authorized or not and you can use the method without the extra parameter,
 * but we need to override it for our /authorize request because our token has not been authorized by this point,
 * yet we still need to sign our /authorize request with both consumer and token secrets.
 */
- (NSString *) oAuthHeaderForMethod:(NSString *)method
							 andUrl:(NSString *)url
						  andParams:(NSDictionary *)params
					 andTokenSecret:(NSString *)token_secret {
    
    [self prepareBasicOAuthDic];

	OAHMAC_SHA1SignatureProvider *sigProvider = [[OAHMAC_SHA1SignatureProvider alloc] init];
	
	// If there were any params, URLencode them.
	NSMutableDictionary *_params = [NSMutableDictionary dictionaryWithCapacity:[params count]];
	if (params) {
		for (NSString *key in [params allKeys]) {
			[_params setObject:[[params objectForKey:key] URLEncodedString] forKey:key];
		}
	}
	
	// Given a signature base and secret key, calculate the signature.
    if (!token_secret) {
        token_secret = @"";
    }
    NSString *signSceret = [NSString stringWithFormat:@"%@&%@",_consumer.secret, token_secret];
	NSString *oauth_signature = [sigProvider
								 signClearText:[self oauth_signature_base:method
																  withUrl:url
																andParams:_params]
								 withSecret:signSceret];
	[sigProvider release];
	
	return [self oauth_authorization_header:oauth_signature withParams:_params];
}

- (NSString *) oAuthSignatureForMethod:(NSString *)method 
                                andUrl:(NSString *)url 
                             andParams:(NSDictionary *)params
                        andTokenSecret:(NSString *)token_secret {
    [self prepareBasicOAuthDic];
    
	OAHMAC_SHA1SignatureProvider *sigProvider = [[OAHMAC_SHA1SignatureProvider alloc] init];
	
	// If there were any params, URLencode them.
	NSMutableDictionary *_params = [NSMutableDictionary dictionaryWithCapacity:[params count]];
	if (params) {
		for (NSString *key in [params allKeys]) {
			[_params setObject:[[params objectForKey:key] URLEncodedString] forKey:key];
		}
	}
	
	// Given a signature base and secret key, calculate the signature.
    if (!token_secret) {
        token_secret = @"";
    }
    NSString *signSceret = [NSString stringWithFormat:@"%@&%@",_consumer.secret, token_secret];
	NSString *oauth_signature = [sigProvider
								 signClearText:[self oauth_signature_base:method
																  withUrl:url
																andParams:_params]
								 withSecret:signSceret];
	[sigProvider release];
    return oauth_signature;
}

#pragma mark -
#pragma mark Internal utilities for crypto, signing.

-(void)prepareBasicOAuthDic {
    [_basicOAuthDic release];
    _basicOAuthDic =[[NSMutableDictionary alloc] initWithCapacity:10];
    
    // Freshen the context. Get a fresh timestamp and calculate a nonce.
	// Nonce algorithm is sha1(timestamp || random), i.e
	// we concatenate timestamp with a random string, and then sha1 it.
    int timestamp = time(NULL);
	NSString *oauth_timestamp = [NSString stringWithFormat:@"%d", timestamp];
    /**
     腾讯微博对这个oauth_nonce不知道有什么限制，这里copy了从QQWeibo SDK的代码
     */
	NSString *oauth_nonce =[NSString stringWithFormat:@"%u", arc4random() % (9999999 - 123400) + 123400];
    
    [_basicOAuthDic setObject:_consumer.key forKey:@"oauth_consumer_key"];
    [_basicOAuthDic setObject:oauth_nonce forKey:@"oauth_nonce"];
    [_basicOAuthDic setObject:@"HMAC-SHA1" forKey:@"oauth_signature_method"];
    [_basicOAuthDic setObject:oauth_timestamp forKey: @"oauth_timestamp"];
    [_basicOAuthDic setObject:@"1.0" forKey:@"oauth_version"];
}

/**
 * Given a HTTP method, URL and a set of parameters, calculate the signature base string according to the spec.
 * Some ideas for the implementation come from OAMutableUrlRequest
 * (http://oauth.googlecode.com/svn/code/obj-c/OAuthConsumer/OAMutableURLRequest.m).
 */
- (NSString *) oauth_signature_base:(NSString *)httpMethod withUrl:(NSString *)url andParams:(NSDictionary *)params {    
	NSMutableDictionary *parts = [_basicOAuthDic mutableCopy];	
	if (params) {		
		[parts addEntriesFromDictionary:params];
	}
	
	// Sort the base string components and make them into string key=value pairs.
	NSMutableArray *normalizedBase = [NSMutableArray arrayWithCapacity:[parts count]];
	for (NSString *key in [[parts allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
		[normalizedBase addObject:[NSString stringWithFormat:@"%@=%@", key, [parts objectForKey:key]]];
	}
	
	NSString *normalizedRequestParameters = [normalizedBase componentsJoinedByString:@"&"];
	
	// Return the signature base string. Note that the individual parameters must have previously
	// already URL-encoded and here we are encoding them again; thus you will see some
	// double URL-encoding for params. This is normal.
	NSString *ret = [NSString stringWithFormat:@"%@&%@&%@",
            httpMethod,
            [url URLEncodedString],
            [normalizedRequestParameters URLEncodedString]];
	return ret;
}

/**
 * Given a calculated signature (by this point it is Base64-encoded string) and a set of parameters, return
 * the header value that you will stick in the "Authorization" header.
 */
- (NSString *) oauth_authorization_header:(NSString *)oauth_signature withParams:(NSDictionary *)params {
    NSMutableDictionary *parts = [_basicOAuthDic mutableCopy];	
    for (NSString *key in [params allKeys])	{	
        if ([key hasPrefix:@"oauth"]) {
            [parts setObject:[params objectForKey:key] forKey:key];
        }
	}	

	NSMutableArray *chunks = [NSMutableArray arrayWithCapacity:[parts count]];
    
    // First add all the base components.
	[chunks addObject:[NSString stringWithString:@"OAuth realm=\"\""]];
    
    // Sort the base string components and make them into string key=value pairs.
	for (NSString *key in [[parts allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
		[chunks addObject:[NSString stringWithFormat:@"%@=\"%@\"", key, [[parts objectForKey:key] URLEncodedString]]];
	}
	
    // Signature will be the last component of our header.
	[chunks addObject:[NSString stringWithFormat:@"%@=\"%@\"", @"oauth_signature", [oauth_signature URLEncodedString]]];
	
	return [NSString stringWithFormat:@"%@", [chunks componentsJoinedByString:@", "]];
}


// http://stackoverflow.com/questions/1353771/trying-to-write-nsstring-sha1-function-but-its-returning-null
- (NSString *)sha1:(NSString *)str {
	const char *cStr = [str UTF8String];
	unsigned char result[CC_SHA1_DIGEST_LENGTH];
	CC_SHA1(cStr, strlen(cStr), result);
	NSMutableString *out = [NSMutableString stringWithCapacity:20];
	for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
		[out appendFormat:@"%02X", result[i]];
	}
	return [out lowercaseString];
}

@end
